﻿using System;
using System.IO;
using System.Linq.Expressions;
using System.Runtime.InteropServices;

namespace ZXthex
{

    abstract class FromC : IDisposable
    {

        class ConnectorFromC
        {
            const string k_temp_folder = "Temp";
            const string k_temp_dll_prefix = " -----";

            [DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]
            static extern IntPtr LoadLibrary(
              [MarshalAs(UnmanagedType.LPStr)] string lpLibFileName);

            [DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
            static extern IntPtr GetProcAddress(IntPtr hModule,
                [MarshalAs(UnmanagedType.LPStr)] string lpProcName);

            [DllImport("kernel32.dll", EntryPoint = "FreeLibrary")]
            static extern bool FreeLibrary(IntPtr hModule);
            
            /// <summary>
            /// 在首次调用时，会检查multiCopy的文件夹是否存在垃圾，并进行清理
            /// </summary>
            static bool firstInvoke;

            static ConnectorFromC() => firstInvoke = true;

            public static ConnectorFromC GetConnectorFromC(string dllPath, bool multiCopy)
            {
                if (multiCopy && firstInvoke)
                {
                    if (Directory.Exists(k_temp_folder))
                    {
                        var files = Directory.GetFiles(k_temp_folder, k_temp_dll_prefix +"*.dll");
                        for (int i = 0; i < files.Length; i++)
                        {
                            File.Delete(files[i]);
                        }
                    }
                    firstInvoke = false;
                }

                return new ConnectorFromC(dllPath, multiCopy);
            }


            IntPtr hModule;

            bool disposed;

            string dllPath;
            bool shouldDelete;

            bool PointerNull(IntPtr ptr) => ptr == IntPtr.Zero;

            ConnectorFromC(string dllPath, bool multiCopy)
            {
                if (multiCopy)
                {
                    var copy = File.ReadAllBytes(dllPath);
                    if (!Directory.Exists(k_temp_folder))
                    {
                        Directory.CreateDirectory(k_temp_folder);
                    }

                    long x = DateTime.Now.ToBinary();
                    do
                    {
                        x++;
                        dllPath = k_temp_folder +"/"+ k_temp_dll_prefix + x.ToString("x") + "- " + Path.GetFileName(dllPath);
                    } while (File.Exists(dllPath));

                    File.WriteAllBytes(dllPath, copy);
                    shouldDelete = true;
                }

                hModule = LoadLibrary(dllPath);
                if (PointerNull(hModule))
                    UnityEngine.Debug.LogError("Load Failed");

                this.dllPath = dllPath;
            }

            public T GetFunction<T>(string functionName) where T : MulticastDelegate
            {
                var ptr = GetProcAddress(hModule, functionName);
                if (PointerNull(ptr))
                {
                    UnityEngine.Debug.LogError("can't find point!");
                    return null;
                }
                //为防止内存泄漏， 用表达式树创建一个如果disposed则调用时会抛出异常的委托
                var _delegate = Marshal.GetDelegateForFunctionPointer<T>(ptr);

                if (_delegate == null)
                {
                    UnityEngine.Debug.LogError("can't get delegate!");
                    return null;
                }

                var parameters = _delegate.GetType().GetMethod("Invoke").GetParameters();

                ParameterExpression[] pes = new ParameterExpression[parameters.Length];
                for (int i = 0; i < parameters.Length; i++)
                {
                    pes[i] = ParameterExpression.Parameter(parameters[i].ParameterType);
                }

                Expression<Func<bool>> condition = () => disposed;

                var block = Expression.Block(
                        Expression.IfThen(Expression.Invoke(condition)
                        ,
                        Expression.Throw(Expression.Constant(new ObjectDisposedException(dllPath)))
                        ),
                        Expression.Invoke(Expression.Constant(_delegate), pes)
                    );

                return Expression.Lambda<T>(block, pes).Compile();

            }


            public void Dispose()
            {
                if (disposed) return;


                var result = FreeLibrary(hModule);

                if (!result)
                    UnityEngine.Debug.LogError("Not free yet !");
                else if (shouldDelete)
                    File.Delete(dllPath);

                disposed = true;
            }

            ~ConnectorFromC()
            {
                if (!disposed)
                {
                    Dispose();
                }
            }
        }

        ConnectorFromC connector;

        public FromC(string dllPath, bool multiCopy = true)
        {
            connector = ConnectorFromC.GetConnectorFromC(dllPath, multiCopy);
        }

        public T GetFunction<T>(string functionName) where T : MulticastDelegate
        {
            return connector.GetFunction<T>(functionName);
        }

        public void Dispose()
        {
            if (connector == null) return;

            connector.Dispose();
            connector = null;
        }

        ~FromC()
        {
            Dispose();
        }
    }
}